﻿using System;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Collections;
using System.Collections.Generic;
using System.Configuration;
using System.Globalization;
using System.IO;
using System.Security.Permissions;
using System.Text;
using System.Web;
using System.Web.Configuration;
using System.Web.Profile;

namespace WebProfileBuilder {
    [PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")]
    internal class Builder {
        private BuildWebProfile _buildWebProfile;

        private Configuration _currentConfig;

        private string _fileExt;

        private ProfileSection _profileConfigSection;

        private string _rootDirectory;

        private string _userFilePath;

        private Builder() {
        }

        /// <summary>
        /// Generate Create function for creating a profile for a user
        /// </summary>
        private static void AddCodeForGetProfileForUser(CodeTypeDeclaration type) {
            CodeMemberMethod method = new CodeMemberMethod();
            method.Name = "GetProfile";
            method.Attributes = MemberAttributes.Public | MemberAttributes.Static;
            method.ReturnType = new CodeTypeReference(type.Name);
            method.Parameters.Add(new CodeParameterDeclarationExpression(typeof(string), "username"));

            CodeMethodInvokeExpression cmie = new CodeMethodInvokeExpression();
            cmie.Method.TargetObject = new CodeTypeReferenceExpression(typeof(ProfileBase));
            cmie.Method.MethodName = "Create";
            cmie.Parameters.Add(new CodeArgumentReferenceExpression("username"));

            CodeMethodReturnStatement returnSatement = new CodeMethodReturnStatement(new CodeObjectCreateExpression(method.ReturnType, new CodeExpression[] {
                cmie
            }));

            method.Statements.Add(returnSatement);
            type.Members.Add(method);
        }

        /// <summary>
        /// Generate Create function for creating a profile for a user
        /// </summary>
        private static void AddCodeForGetProfileForUser2(CodeTypeDeclaration type) {
            CodeMemberMethod method = new CodeMemberMethod();
            method.Name = "GetProfile";
            method.Attributes = MemberAttributes.Public | MemberAttributes.Static;
            method.ReturnType = new CodeTypeReference(type.Name);
            method.Parameters.Add(new CodeParameterDeclarationExpression(typeof(string), "username"));
            method.Parameters.Add(new CodeParameterDeclarationExpression(typeof(bool), "authenticated"));

            CodeMethodInvokeExpression cmie = new CodeMethodInvokeExpression();
            cmie.Method.TargetObject = new CodeTypeReferenceExpression(typeof(ProfileBase));
            cmie.Method.MethodName = "Create";
            cmie.Parameters.Add(new CodeArgumentReferenceExpression("username"));
            cmie.Parameters.Add(new CodeArgumentReferenceExpression("authenticated"));

            CodeMethodReturnStatement returnSatement = new CodeMethodReturnStatement(new CodeObjectCreateExpression(method.ReturnType, new CodeExpression[] {
                cmie
            }));

            method.Statements.Add(returnSatement);
            type.Members.Add(method);
        }

        /// <summary>
        /// Creates in indexed property that delegates to the base indexed property
        /// </summary>
        private static void AddDelegatingIndexedProperty(CodeTypeDeclaration ctd, string fieldName, Type type, Type indexType, string indexName) {
            CodeMemberProperty prop = new CodeMemberProperty();
            prop.Name = "Item";
            prop.Attributes = MemberAttributes.Public;
            prop.HasGet = true;
            prop.HasSet = true;
            prop.Type = new CodeTypeReference(type);
            prop.Parameters.Add(new CodeParameterDeclarationExpression(indexType, indexName));

            CodeMethodReturnStatement ret =
                new CodeMethodReturnStatement(
                    new CodeIndexerExpression(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName),
                        new CodeExpression[] {
                            new CodeVariableReferenceExpression(indexName)
                        }));
            prop.GetStatements.Add(ret);

            CodeAssignStatement asn =
                new CodeAssignStatement(
                    new CodeIndexerExpression(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName),
                        new CodeExpression[] {
                            new CodeVariableReferenceExpression(indexName)
                        }),
                    new CodePropertySetValueReferenceExpression());
            prop.SetStatements.Add(asn);

            ctd.Members.Add(prop);
        }

        /// <summary>
        /// Creates a method that delegates to the base
        /// </summary>
        private static void AddDelegatingMethod(CodeTypeDeclaration ctd, string fieldName, string name, Type returnType, Type[] paramTypes, string[] paramNames) {
            CodeMemberMethod method = new CodeMemberMethod();
            method.Name = name;
            method.Attributes = MemberAttributes.Public;

            CodeMethodInvokeExpression cmie = new CodeMethodInvokeExpression();
            cmie.Method.TargetObject = new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName);
            cmie.Method.MethodName = name;

            if (paramTypes != null) {
                for (int i = 0; i < paramTypes.Length; i++) {
                    method.Parameters.Add(new CodeParameterDeclarationExpression(paramTypes[i], paramNames[i]));
                    cmie.Parameters.Add(new CodeArgumentReferenceExpression(paramNames[i]));
                }
            }

            if (returnType != null) {
                method.ReturnType = new CodeTypeReference(returnType);
                CodeMethodReturnStatement ret = new CodeMethodReturnStatement(cmie);
                method.Statements.Add(ret);
            }
            else {
                method.Statements.Add(cmie);
            }

            ctd.Members.Add(method);
        }

        /// <summary>
        /// Creates a read-only property that delegates to the base
        /// </summary>
        private static void AddDelegatingReadOnlyProperty(CodeTypeDeclaration ctd, string fieldName, string name, Type type) {
            CodeMemberProperty prop = new CodeMemberProperty();
            prop.Name = name;
            prop.Attributes = MemberAttributes.Public;
            prop.HasGet = true;
            prop.Type = new CodeTypeReference(type);

            CodeMethodReturnStatement ret =
                new CodeMethodReturnStatement(
                    new CodePropertyReferenceExpression(
                        new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName), name));
            prop.GetStatements.Add(ret);

            ctd.Members.Add(prop);
        }

        /// <summary>
        /// Creates standard methods of ProfileBase that delegate to base
        /// </summary>
        private static void AddProfileBaseDelegators(CodeTypeDeclaration ctd) {
            // Add field
            string fieldName = "_profileBase";
            ctd.Members.Add(new CodeMemberField(typeof(ProfileBase), fieldName));

            // Add default constructor
            CodeConstructor ctor = new CodeConstructor();
            ctor.Attributes = MemberAttributes.Public;
            ctor.Statements.Add(
                new CodeAssignStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName),
                    new CodeObjectCreateExpression(typeof(ProfileBase), new CodeExpression[] {
                    })));
            ctd.Members.Add(ctor);

            // Add proxy constructor
            ctor = new CodeConstructor();
            ctor.Attributes = MemberAttributes.Public;
            ctor.Parameters.Add(
                new CodeParameterDeclarationExpression(new CodeTypeReference(typeof(ProfileBase)), "profileBase"));
            ctor.Statements.Add(
                new CodeAssignStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName),
                    new CodeVariableReferenceExpression("profileBase")));
            ctd.Members.Add(ctor);

            // Add Current accessor
            CodeMemberProperty prop = new CodeMemberProperty();
            prop.Attributes = MemberAttributes.Public | MemberAttributes.Static;
            prop.HasGet = true;
            prop.Name = "Current";
            prop.Type = new CodeTypeReference(ctd.Name);
            prop.GetStatements.Add(
                new CodeMethodReturnStatement(
                    new CodeObjectCreateExpression(ctd.Name,
                        new CodeExpression[] {
                            new CodePropertyReferenceExpression(
                                new CodePropertyReferenceExpression(
                                    new CodeTypeReferenceExpression(typeof(HttpContext)), "Current"),
                                "Profile")
                        })));
            ctd.Members.Add(prop);

            // Add ProfileBase accessor
            prop = new CodeMemberProperty();
            prop.Attributes = MemberAttributes.Public;
            prop.HasGet = true;
            prop.Name = "ProfileBase";
            prop.Type = new CodeTypeReference(typeof(ProfileBase));
            prop.GetStatements.Add(
                new CodeMethodReturnStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName)));
            ctd.Members.Add(prop);

            // Add ProfileBase delegators
            AddDelegatingIndexedProperty(ctd, fieldName, typeof(object), typeof(string), "propertyName");
            AddDelegatingMethod(ctd, fieldName, "GetPropertyValue", typeof(object), new[] {
                typeof(string)
            },
                new[] {
                    "propertyName"
                });
            AddDelegatingMethod(ctd, fieldName, "SetPropertyValue", null, new[] {
                typeof(string), typeof(object)
            },
                new[] {
                    "propertyName", "propertyValue"
                });
            AddDelegatingMethod(ctd, fieldName, "GetProfileGroup", typeof(ProfileGroupBase), new[] {
                typeof(string)
            },
                new[] {
                    "groupName"
                });
            AddDelegatingMethod(ctd, fieldName, "Initialize", null, new[] {
                typeof(string), typeof(bool)
            },
                new[] {
                    "username", "isAuthenticated"
                });
            AddDelegatingMethod(ctd, fieldName, "Save", null, null, null);
            AddDelegatingMethod(ctd, fieldName, "Initialize", null,
                new[] {
                    typeof(SettingsContext), typeof(SettingsPropertyCollection),
                    typeof(SettingsProviderCollection)
                }, new[] {
                    "context", "properties", "providers"
                });
            AddStaticDelegatingMethod(ctd, typeof(ProfileBase), "Synchronized", typeof(SettingsBase),
                new[] {
                    typeof(SettingsBase)
                }, new[] {
                    "settingsBase"
                });
            AddStaticDelegatingMethod(ctd, typeof(ProfileBase), "Create", typeof(ProfileBase), new[] {
                typeof(string)
            },
                new[] {
                    "userName"
                });
            AddStaticDelegatingMethod(ctd, typeof(ProfileBase), "Create", typeof(ProfileBase),
                new[] {
                    typeof(string), typeof(bool)
                }, new[] {
                    "userName", "isAuthenticated"
                });
            AddDelegatingReadOnlyProperty(ctd, fieldName, "UserName", typeof(string));
            AddDelegatingReadOnlyProperty(ctd, fieldName, "IsAnonymous", typeof(bool));
            AddDelegatingReadOnlyProperty(ctd, fieldName, "IsDirty", typeof(bool));
            AddDelegatingReadOnlyProperty(ctd, fieldName, "LastActivityDate", typeof(DateTime));
            AddDelegatingReadOnlyProperty(ctd, fieldName, "LastUpdatedDate", typeof(DateTime));
            AddDelegatingReadOnlyProperty(ctd, fieldName, "Providers", typeof(SettingsProviderCollection));
            AddDelegatingReadOnlyProperty(ctd, fieldName, "PropertyValues", typeof(SettingsPropertyValueCollection));
            AddDelegatingReadOnlyProperty(ctd, fieldName, "Context", typeof(SettingsContext));
            AddDelegatingReadOnlyProperty(ctd, fieldName, "IsSynchronized", typeof(bool));
            AddStaticDelegatingReadOnlyProperty(ctd, typeof(ProfileBase), "Properties", typeof(SettingsPropertyCollection));
        }

        /// <summary>
        /// Creates standard methods of ProfileGroupBase that delegate to base
        /// </summary>
        private static void AddProfileGroupBaseDelegators(CodeTypeDeclaration ctd) {
            // Add field
            string fieldName = "_profileGroupBase";
            ctd.Members.Add(new CodeMemberField(typeof(ProfileGroupBase), fieldName));

            // Add default constructor
            CodeConstructor ctor = new CodeConstructor();
            ctor.Attributes = MemberAttributes.Public;
            ctor.Statements.Add(
                new CodeAssignStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName),
                    new CodeObjectCreateExpression(new CodeTypeReference(typeof(ProfileGroupBase)),
                        new CodeExpression[] {
                        })));
            ctd.Members.Add(ctor);

            // Add proxy constructor
            ctor = new CodeConstructor();
            ctor.Attributes = MemberAttributes.Public;
            ctor.Parameters.Add(
                new CodeParameterDeclarationExpression(new CodeTypeReference(typeof(ProfileGroupBase)), "profileGroupBase"));
            ctor.Statements.Add(
                new CodeAssignStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName),
                    new CodeVariableReferenceExpression("profileGroupBase")));
            ctd.Members.Add(ctor);

            // Add ProfileGroupBase accessor
            CodeMemberProperty prop = new CodeMemberProperty();
            prop.Attributes = MemberAttributes.Public;
            prop.HasGet = true;
            prop.Name = "ProfileGroupBase";
            prop.Type = new CodeTypeReference(typeof(ProfileGroupBase));
            prop.GetStatements.Add(
                new CodeMethodReturnStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName)));
            ctd.Members.Add(prop);

            // Add ProfileGroupBase delegators
            AddDelegatingIndexedProperty(ctd, fieldName, typeof(object), typeof(string), "propertyName");
            AddDelegatingMethod(ctd, fieldName, "GetPropertyValue", typeof(object), new[] {
                typeof(string)
            },
                new[] {
                    "propertyName"
                });
            AddDelegatingMethod(ctd, fieldName, "SetPropertyValue", null, new[] {
                typeof(string), typeof(object)
            },
                new[] {
                    "propertyName", "propertyValue"
                });
            AddDelegatingMethod(ctd, fieldName, "Init", null, new[] {
                typeof(ProfileBase), typeof(string)
            },
                new[] {
                    "parent", "myName"
                });
        }

        /// <summary>
        /// Collects property data from config collection
        /// </summary>
        private void AddProfilePropertySettingsForCompilation(ProfilePropertySettingsCollection propertyCollection, Hashtable ht, string groupName) {
            foreach (ProfilePropertySettings pps in propertyCollection) {
                ProfileNameType prop = new ProfileNameType();
                if (groupName != null) {
                    prop.Name = groupName + "." + pps.Name;
                }
                else {
                    prop.Name = pps.Name;
                }

                // First, try to resolve simple types
                Type t = ResolvePropertyTypeForCommonTypes(pps.Type.ToLower(CultureInfo.InvariantCulture));

                // Second, try to resolve using project references
                //if (t == null) {
                //    t = TypeResolutionService.GetType(pps.Type, false);
                //}

                if (t != null) {
                    // Reference type
                    prop.PropertyCodeRefType = new CodeTypeReference(t);
                }
                else {
                    // Reference type name
                    string typeName = pps.Type;
                    if (!string.IsNullOrEmpty(typeName)) {
                        int firstComma = typeName.IndexOf(',');
                        if (firstComma > 0) {
                            // Strip off assembly info
                            typeName = typeName.Substring(0, firstComma).Trim();
                        }
                        else {
                            typeName = typeName.Trim();
                        }
                    }
                    prop.PropertyCodeRefType = new CodeTypeReference(typeName);
                }

                //prop.PropertyType = t;
                prop.IsReadOnly = pps.ReadOnly;
                //prop.LineNumber = pps.ElementInformation.Properties["name"].LineNumber;
                //prop.FileName = pps.ElementInformation.Properties["name"].Source;
                ht.Add(prop.Name, prop);
            }
        }

        /// <summary>
        /// Creates a property accessor for a group and creates the group class
        /// </summary>
        private static void AddPropertyGroup(string groupName, string propertyNames, Hashtable properties, CodeTypeDeclaration type, CodeNamespace ns) {
            // public  property.Type property.name {
            CodeMemberProperty prop = new CodeMemberProperty();
            prop.Name = groupName;
            prop.Attributes = MemberAttributes.Public;
            prop.HasGet = true;
            prop.Type = new CodeTypeReference("WebProfileGroup" + groupName);

            CodeMethodInvokeExpression cmie;
            CodeMethodReturnStatement getLine;

            cmie = new CodeMethodInvokeExpression();
            cmie.Method.TargetObject = new CodeThisReferenceExpression();
            cmie.Method.MethodName = "GetProfileGroup";
            cmie.Parameters.Add(new CodePrimitiveExpression(prop.Name));
            getLine = new CodeMethodReturnStatement(new CodeObjectCreateExpression(prop.Type, cmie));

            prop.GetStatements.Add(getLine);
            type.Members.Add(prop);

            // public class WebProfileGroupGroupName
            CodeTypeDeclaration grpType = new CodeTypeDeclaration();
            grpType.Name = "WebProfileGroup" + groupName;

            string[] grpProps = propertyNames.Split(';');
            foreach (string grpProp in grpProps) {
                CreateCodeForProperty(grpType, (ProfileNameType)properties[grpProp]);
            }

            AddProfileGroupBaseDelegators(grpType);

            ns.Types.Add(grpType);
        }

        /// <summary>
        /// Creates a static method that delegates to the base
        /// </summary>
        private static void AddStaticDelegatingMethod(CodeTypeDeclaration ctd, Type typeDelegate, string name, Type returnType, Type[] paramTypes, string[] paramNames) {
            CodeMemberMethod method = new CodeMemberMethod();
            method.Name = name;
            method.Attributes = MemberAttributes.Public | MemberAttributes.Static;

            CodeMethodInvokeExpression cmie = new CodeMethodInvokeExpression();
            cmie.Method.TargetObject = new CodeTypeReferenceExpression(typeDelegate);
            cmie.Method.MethodName = name;

            if (paramTypes != null) {
                for (int i = 0; i < paramTypes.Length; i++) {
                    method.Parameters.Add(new CodeParameterDeclarationExpression(paramTypes[i], paramNames[i]));
                    cmie.Parameters.Add(new CodeArgumentReferenceExpression(paramNames[i]));
                }
            }

            if (returnType != null) {
                method.ReturnType = new CodeTypeReference(returnType);
                CodeMethodReturnStatement ret = new CodeMethodReturnStatement(cmie);
                method.Statements.Add(ret);
            }
            else {
                method.Statements.Add(cmie);
            }

            ctd.Members.Add(method);
        }

        /// <summary>
        /// Create a static read-only property that delegates to the base
        /// </summary>
        private static void AddStaticDelegatingReadOnlyProperty(CodeTypeDeclaration ctd, Type typeDelegate, string name, Type type) {
            CodeMemberProperty prop = new CodeMemberProperty();
            prop.Name = name;
            prop.Attributes = MemberAttributes.Public | MemberAttributes.Static;
            prop.HasGet = true;
            prop.Type = new CodeTypeReference(type);

            CodeMethodReturnStatement ret =
                new CodeMethodReturnStatement(
                    new CodePropertyReferenceExpression(new CodeTypeReferenceExpression(typeDelegate), name));
            prop.GetStatements.Add(ret);

            ctd.Members.Add(prop);
        }

        /// <summary>
        /// Creates a strong type property accessor
        /// </summary>
        private static void CreateCodeForProperty(CodeTypeDeclaration type, ProfileNameType property) {
            string name = property.Name;
            int pos = name.IndexOf('.');
            if (pos > 0) {
                name = name.Substring(pos + 1);
            }

            CodeMemberProperty prop = new CodeMemberProperty();
            prop.Name = name;
            prop.Attributes = MemberAttributes.Public;
            prop.HasGet = true;
            prop.Type = property.PropertyCodeRefType;

            CodeMethodInvokeExpression cmie;
            CodeMethodReturnStatement getLine;

            cmie = new CodeMethodInvokeExpression();
            cmie.Method.TargetObject = new CodeThisReferenceExpression();
            cmie.Method.MethodName = "GetPropertyValue";
            cmie.Parameters.Add(new CodePrimitiveExpression(name));
            getLine = new CodeMethodReturnStatement(new CodeCastExpression(prop.Type, cmie));

            prop.GetStatements.Add(getLine);

            if (!property.IsReadOnly) {
                CodeMethodInvokeExpression setLine;

                setLine = new CodeMethodInvokeExpression();
                setLine.Method.TargetObject = new CodeThisReferenceExpression();
                setLine.Method.MethodName = "SetPropertyValue";
                setLine.Parameters.Add(new CodePrimitiveExpression(name));
                setLine.Parameters.Add(new CodePropertySetValueReferenceExpression());
                prop.HasSet = true;
                prop.SetStatements.Add(setLine);
            }
            type.Members.Add(prop);
        }

        public static void Generate(BuildWebProfile buildWebProfile) {
            (new Builder()).GenerateWebProfile(buildWebProfile);
        }

        /// <summary>
        /// Calls to generate CodeCompile unit and then converts code dom
        /// to string using CodeDomProvider.
        /// </summary>
        private string GenerateCode() {
            // Generate the profile code dom tree
            CodeCompileUnit codeCompileUnit = GenerateCodeCompileUnit();

            // Convert code dom tree to code in current CodeProvider language
            string code;
            using (StringWriter sw = new StringWriter(CultureInfo.InvariantCulture)) {
                CodeDomProvider codeProvider;
                switch (_buildWebProfile.Language) {
                    case "c#":
                    case "csharp":
                        codeProvider = CodeDomProvider.CreateProvider("CSharp");
                        break;
                    case "vb":
                    case "visualbasic":
                    case "visual basic":
                        codeProvider = CodeDomProvider.CreateProvider("VisualBasic");
                        break;
                    default:
                        codeProvider = CodeDomProvider.CreateProvider("CSharp");
                        break;
                }

                codeProvider.GenerateCodeFromCompileUnit(codeCompileUnit, sw, null);
                code = sw.ToString();
            }

            // Return code as string
            return code;
        }

        /// <summary>
        /// Generate the code compile unit
        /// </summary>
        internal CodeCompileUnit GenerateCodeCompileUnit() {
            Hashtable properties = GetPropertiesForCompilation(_profileConfigSection);
            CodeCompileUnit compileUnit = new CodeCompileUnit();
            Hashtable groups = new Hashtable();

            CodeNamespace ns = new CodeNamespace();
            ns.Name = ProfileNameSpace;

            ns.Imports.Add(new CodeNamespaceImport("System"));
            ns.Imports.Add(new CodeNamespaceImport("System.Web"));
            ns.Imports.Add(new CodeNamespaceImport("System.Web.Profile"));
            ns.Imports.Add(new CodeNamespaceImport("System.Configuration"));

            CodeTypeDeclaration type = new CodeTypeDeclaration();
            type.Name = ProfileClassName;
            type.Attributes = MemberAttributes.Public;
            type.IsPartial = true;

            foreach (DictionaryEntry de in properties) {
                ProfileNameType property = (ProfileNameType)de.Value;

                int pos = property.Name.IndexOf('.');
                if (pos < 0) {
                    CreateCodeForProperty(type, property);
                }
                else {
                    string grpName = property.Name.Substring(0, pos);
                    if (groups[grpName] == null) {
                        groups.Add(grpName, property.Name);
                    }
                    else {
                        groups[grpName] = ((string)groups[grpName]) + ";" + property.Name;
                    }
                }
            }

            foreach (DictionaryEntry de in groups) {
                AddPropertyGroup((string)de.Key, (string)de.Value, properties, type, ns);
            }

            AddCodeForGetProfileForUser(type);
            AddCodeForGetProfileForUser2(type);

            // Add other members
            AddProfileBaseDelegators(type);

            ns.Types.Add(type);
            compileUnit.Namespaces.Add(ns);

            return compileUnit;
        }

        private void GenerateWebProfile(BuildWebProfile buildWebProfile) {
            _buildWebProfile = buildWebProfile;

            Initialize(_buildWebProfile.WebSiteRoot);

            //If no changes are found, get out now.
            bool noChanges = IsProfileSame();
            if (noChanges) {
                return;
            }

            //Make sure the file exists and is not read only.
            if (File.Exists(ProfileFullPath)) {
                bool isReadOnly = new FileInfo(ProfileFullPath).IsReadOnly;
                if (isReadOnly) {
                    _buildWebProfile.Log.LogWarning("Cannot write Profile class. File is read only. \"{0}\"", ProfileFullPath);
                    return;
                }
            }

            //Save the user cache file to compare later.
            WriteUserCacheFile();

            // Generate the code
            string code = GenerateCode();

            // Save the WebProfile code
            SaveWebProfile(code);
        }

        /// <summary>
        /// Collects property data from config
        /// </summary>
        private Hashtable GetPropertiesForCompilation(ProfileSection config) {
            Hashtable ht = new Hashtable();
            if (config.PropertySettings != null) {
                AddProfilePropertySettingsForCompilation(config.PropertySettings, ht, null);

                foreach (ProfileGroupSettings pgs in config.PropertySettings.GroupSettings) {
                    AddProfilePropertySettingsForCompilation(pgs.PropertySettings, ht, pgs.Name);
                }
            }
            return ht;
        }

        /// <summary>
        /// Initializes the members of the class.
        /// </summary>
        /// <param name="directory">Directory that contains the web.config</param>
        private void Initialize(string directory) {
            _rootDirectory = directory;
            _userFilePath = Path.Combine(directory, "WebProfileBuilder.user");

            #region "Load the ConfigurationSection Objects"

            WebConfigurationFileMap webMap = new WebConfigurationFileMap();
            VirtualDirectoryMapping vDirMap = new VirtualDirectoryMapping(directory, true);
            webMap.VirtualDirectories.Add("/", vDirMap);

            _currentConfig = WebConfigurationManager.OpenMappedWebConfiguration(webMap, "/");

            if (_currentConfig != null) {
                _profileConfigSection = (ProfileSection)_currentConfig.GetSection("system.web/profile");
            }

            #endregion

            #region "Set the file extension."

            //Set file ext.
            switch (_buildWebProfile.Language) {
                case "c#":
                case "csharp":
                    _fileExt = ".cs";
                    break;
                case "vb":
                case "visualbasic":
                case "visual basic":
                    _fileExt = ".vb";
                    break;
                default:
                    _fileExt = ".cs";
                    break;
            }

            #endregion
        }

        /// <summary>
        /// Checks several conditions to see if the profile section has changed.
        /// Returns true if there are no changes found.
        /// </summary>
        /// <returns></returns>
        private bool IsProfileSame() {
            //if the files have not been created yet, mark as being different.
            if (!File.Exists(_userFilePath) || !File.Exists(ProfileFullPath)) {
                return false;
            }

            // Open the two Streams.
            FileStream streamUserFile = new FileStream(_userFilePath, FileMode.Open);
            MemoryStream streamProfileSection = new MemoryStream(StringToByteArray(_profileConfigSection.SectionInformation.GetRawXml()));

            // Check the file sizes. If they are not the same, the files 
            // are not the same.
            if (streamUserFile.Length != streamProfileSection.Length) {
                // Close the file
                streamUserFile.Close();
                streamProfileSection.Close();
                return false;
            }

            int userFileByte;
            int profileSectionByte;

            // Read and compare a byte from each file until either a
            // non-matching set of bytes is found or until the end of
            // stream is reached.
            do {
                // Read one byte from each file.
                userFileByte = streamUserFile.ReadByte();
                profileSectionByte = streamProfileSection.ReadByte();
            } while ((userFileByte == profileSectionByte) && (userFileByte != -1));

            streamUserFile.Close();
            streamProfileSection.Close();

            return ((userFileByte - profileSectionByte) == 0);
        }

        /// <summary>
        /// Helper to map config type names to actual Type
        /// </summary>
        private static Type ResolvePropertyTypeForCommonTypes(string typeName) {
            switch (typeName) {
                case "string":
                    return typeof(string);

                case "byte":
                case "int8":
                    return typeof(byte);

                case "boolean":
                case "bool":
                    return typeof(bool);

                case "char":
                    return typeof(char);

                case "int":
                case "integer":
                case "int32":
                    return typeof(int);

                case "date":
                case "datetime":
                    return typeof(DateTime);

                case "decimal":
                    return typeof(decimal);

                case "double":
                case "float64":
                    return typeof(Double);

                case "float":
                case "float32":
                    return typeof(float);

                case "long":
                case "int64":
                    return typeof(long);

                case "short":
                case "int16":
                    return typeof(Int16);

                case "single":
                    return typeof(Single);

                case "uint16":
                case "ushort":
                    return typeof(UInt16);

                case "uint32":
                case "uint":
                    return typeof(uint);

                case "ulong":
                case "uint64":
                    return typeof(ulong);

                case "object":
                    return typeof(object);

                default:
                    return null;
            }
        }

        /// <summary>
        /// Write the code to disk.
        /// </summary>
        /// <param name="code">Code to write</param>
        private void SaveWebProfile(string code) {
            using (StreamWriter sw = File.CreateText(ProfileFullPath)) {
                sw.Write(code);
            }
        }

        /// <summary>
        /// Converts a string to a byte array using UTF8Encoding encoding.
        /// </summary>
        /// <param name="input">String to convert.</param>
        /// <returns></returns>
        public static byte[] StringToByteArray(string input) {
            UTF8Encoding encoding = new UTF8Encoding();
            return encoding.GetBytes(input);
        }

        /// <summary>
        /// Writes the "WebProfileBuilder.user" file.
        /// </summary>
        private void WriteUserCacheFile() {
            File.WriteAllText(_userFilePath, _profileConfigSection.SectionInformation.GetRawXml());
        }

        public string ProfileClassName {
            get {
                if (string.IsNullOrEmpty(_buildWebProfile.ClassName)) {
                    return "WebProfile";
                }
                return _buildWebProfile.ClassName;
            }
        }

        public string ProfileDirectory {
            get {
                if (string.IsNullOrEmpty(_buildWebProfile.Directory)) {
                    return _rootDirectory;
                }
                string fullPath = Path.Combine(_rootDirectory, _buildWebProfile.Directory);
                if (!Directory.Exists(fullPath)) {
                    Directory.CreateDirectory(fullPath);
                }
                return fullPath;
            }
        }

        /// <summary>
        /// File Name of the Profile Class.
        /// </summary>
        public string ProfileFileName {
            get {
                if (string.IsNullOrEmpty(_buildWebProfile.FileName)) {
                    return string.Format("WebProfile{0}", _fileExt);
                }
                return string.Format("{0}{1}", _buildWebProfile.FileName, _fileExt);
            }
        }

        public string ProfileFullPath {
            get { return Path.Combine(ProfileDirectory, ProfileFileName); }
        }

        public string ProfileNameSpace {
            get { return _buildWebProfile.RootNamespace; }
        }

        /// <summary>
        /// Holds modified data collected from config
        /// </summary>
        private class ProfileNameType {
            internal bool IsReadOnly;

            internal string Name;

            internal CodeTypeReference PropertyCodeRefType;
        }
    }
}
